﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using DirectCanvas;
using DirectCanvas.Misc;
using System.Diagnostics;
using DirectCanvas.Brushes;
using DirectCanvas.Shapes;
using Blake.NUI.WPF.Utility;
using System.Windows.Threading;
using System.Threading.Tasks;
using Blake.Fluid;

namespace KinectBender
{
    class FluidPhysicsScene : IPhysicsScene
    {
        #region Fields

        bool isRendering = false;

        Size particleSize;
        Size imageSize;
        DirectCanvasFactory factory;
        WPFPresenter presenter;
        DrawingLayer intermediateLayer;
        Brush particleBrush;
        Brush wallBrush;

        int frameCount;
        Stopwatch fpsTimer;

        FluidSystem fluid;

        #endregion

        #region Properties

        public bool IsFountainOn { get; set; }

        public double FPS { get; private set; }

        public bool IsDisposed { get; private set; }

        public System.Windows.Media.ImageSource ImageSource
        {
            get
            {
                return presenter.ImageSource;
            }
        }

        public int NumParticles
        {
            get
            {
                return fluid.Particles.Count;
            }
        }

        #endregion

        #region Events

        public event EventHandler FPSUpdated;

        protected void OnFPSUpdated()
        {
            if (FPSUpdated == null)
                return;
            FPSUpdated(this, EventArgs.Empty);
        }

        #endregion

        #region Constructors

        public FluidPhysicsScene(int width, int height)
        {
            IsDisposed = false;
            Init(new Size(width, height));
        }

        ~FluidPhysicsScene()
        {
            Dispose();
        }

        #endregion

        #region Public Methods

        public void Start()
        {
            CompositionTargetEx.FrameUpdating += CompositionTargetEx_FrameUpdating;
        }

        public void Stop()
        {
            CompositionTargetEx.FrameUpdating -= CompositionTargetEx_FrameUpdating;
        }

        #endregion

        #region Private Methods

        void CountFrames()
        {
            frameCount++;
            if (fpsTimer == null)
            {
                fpsTimer = new Stopwatch();
                fpsTimer.Start();
            }

            if (fpsTimer.Elapsed.TotalSeconds > 1)
            {
                FPS = frameCount / fpsTimer.Elapsed.TotalSeconds;
                OnFPSUpdated();
                fpsTimer.Restart();
                frameCount = 0;
            }
        }

        void Init(Size imageSize)
        {
            this.imageSize = imageSize;

            fluid = new FluidSystem();
            fluid.Initialize(20000);
            fluid.CreateExample();
            fluid.Parameters.ColorMode = ParticleColorMode.Pressure;
            float size = fluid.Parameters.ParticleRadius / fluid.Parameters.SimScale;
            size = (float)MathUtility.MapValue(size, 0, fluid.Parameters.VolumeMax.X - fluid.Parameters.VolumeMin.X + 2, 0, imageSize.Width);
            size = Math.Max(4, size);
            particleSize = new Size((int)size, (int)size);

            factory = new DirectCanvasFactory();
            presenter = new WPFPresenter(factory, imageSize.Width, imageSize.Height);
            intermediateLayer = factory.CreateDrawingLayer(particleSize.Width, particleSize.Height);

            particleBrush = factory.CreateSolidColorBrush(new DirectCanvas.Color4(1f, 1f, 1f, 1f));
            wallBrush = factory.CreateSolidColorBrush(new Color4(1f, 0f, .5f, .5f));

            intermediateLayer.Clear(new DirectCanvas.Color4(0, 0, 0, 0));
            intermediateLayer.BeginDraw();

            Ellipse ellipse = new Ellipse(new PointF(particleSize.Width / 2, particleSize.Height / 2), particleSize.Width / 2, particleSize.Height / 2);
            intermediateLayer.FillEllipse(particleBrush, ellipse);
            intermediateLayer.EndDraw();


        }

        void CompositionTargetEx_FrameUpdating(object sender, DeltaRenderingEventArgs e)
        {
            Step(e.Delta);
        }

        private void Step(TimeSpan delta)
        {
            Update(delta);
            Render();
            CountFrames();
        }

        private void Update(TimeSpan delta)
        {
            //    float factor = 50;
            //Parallel.ForEach(fluid.Particles, particle =>
            //{

            //particle.Body.ApplyForce(new Vector2(-particle.Position.X / factor, -particle.Position.Y / factor));
            //particle.Body.ApplyForce(new Vector2(0, -10f));
            //particle.Body.ApplyTorque(4f);
            //});
            fluid.IsFountainOn = IsFountainOn;
            for (int i = 0; i < 2; i++)
            {
                fluid.Run();
            }
        }

        private void Render()
        {
            if (isRendering)
                return;
            isRendering = true;
            presenter.Clear(new DirectCanvas.Color4(1, 1, 1, 1));

            presenter.BeginDraw();

            var min = fluid.Parameters.VolumeMin;
            var max = fluid.Parameters.VolumeMax;

            DrawRectangle(new PointF(min.X - 1, min.Z - 1), new PointF(min.X, max.Z + 1));
            DrawRectangle(new PointF(min.X - 1, min.Z - 1), new PointF(max.X + 1, min.Z));
            DrawRectangle(new PointF(min.X - 1, max.Z), new PointF(max.X + 1, max.Z + 1));
            DrawRectangle(new PointF(max.X, min.Z - 1), new PointF(max.X + 1, max.Z + 1));

            foreach (var body in fluid.Bodies)
            {
                DrawRectangle(new PointF(body.MinPosition.X, body.MinPosition.Z),
                              new PointF(body.MaxPosition.X, body.MaxPosition.Z));
            }

            presenter.EndDraw();

            presenter.BeginCompose();

            var sourceArea = new RectangleF(0, 0, particleSize.Width, particleSize.Height);
            var rot = new RotationParameters();
            var tint = new Color4(1, 1, 1, 1);
            foreach (var particle in fluid.Particles)
            {
                tint.Blue = particle.Color.Blue;
                tint.Green = particle.Color.Green;
                tint.Red = particle.Color.Red;
                var position = PositionToScreen(particle.Position);
                var destinationArea = new RectangleF(position.X - particleSize.Width / 2, position.Y - particleSize.Height / 2,
                                                    particleSize.Width, particleSize.Height);
                presenter.ComposeLayer(intermediateLayer, sourceArea, destinationArea, rot, tint);
            }

            presenter.EndCompose();
            presenter.Present();
            isRendering = false;
        }

        private void DrawRectangle(PointF min, PointF max)
        {
            float minX = Math.Min(min.X, max.X);
            float minY = Math.Max(min.Y, max.Y);
            float maxX = Math.Max(min.X, max.X);
            float maxY = Math.Min(min.Y, max.Y);

            RectangleF newRect = new RectangleF();
            var volumeMin = fluid.Parameters.VolumeMin;
            var volumeMax = fluid.Parameters.VolumeMax;

            PointF size = new PointF(Math.Abs(maxX - minX), Math.Abs(maxY - minY));

            newRect.X = (float)MathUtility.MapValue(minX, volumeMin.X - 1, volumeMax.X + 1, 0, imageSize.Width);
            newRect.Y = (float)MathUtility.MapValue(minY, volumeMax.Z + 1, volumeMin.Z - 1, 0, imageSize.Height);
            newRect.Width = (float)MathUtility.MapValue(size.X, 0, volumeMax.X - volumeMin.X + 2, 0, imageSize.Width);
            newRect.Height = (float)MathUtility.MapValue(size.Y, 0, volumeMax.Z - volumeMin.Z + 2, 0, imageSize.Height);
            presenter.FillRectangle(wallBrush, newRect);
        }

        private PointF PositionToScreen(SlimDX.Vector3 position)
        {
            float x = position.X;
            float z = position.Z;

            var min = fluid.Parameters.VolumeMin;
            var max = fluid.Parameters.VolumeMax;

            x = (float)MathUtility.MapValue(x, min.X - 1, max.X + 1, 0, imageSize.Width);
            z = (float)MathUtility.MapValue(z, max.Z + 1, min.Z - 1, 0, imageSize.Height);

            return new PointF(x, z);
        }

        #endregion

        public void Dispose()
        {
            if (IsDisposed)
                return;
            IsDisposed = true;

            if (intermediateLayer != null) { intermediateLayer.Dispose(); intermediateLayer = null; }
            if (intermediateLayer != null) { intermediateLayer.Dispose(); intermediateLayer = null; }
            if (particleBrush != null) { particleBrush.Dispose(); particleBrush = null; }
            if (wallBrush != null) { wallBrush.Dispose(); wallBrush = null; }
            if (presenter != null) { presenter.Dispose(); presenter = null; }
            if (factory != null) { factory.Dispose(); factory = null; }
        }

    }
}
